package br.unifor.mia.sd.proxy;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Hashtable;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.xml.sax.SAXException;

public class HttpMessage {

	String method = null;

	String body = null;

	String url = null;

	String host = null;

	Hashtable<String, String> headers = new Hashtable<String, String>();

	int port = 80;

	byte[] message = null;

	boolean wsCall = false;

	Logger logger = Logger.getLogger("br.unifor.mia.sd.proxy");

	/** Abstraction of Soap Message found in Http packet */
	private Document soapMessage = null;

	/**
	 * <p>
	 * Constructs and HTTP Message representation.
	 * </p>
	 * <p>
	 * The message is parsed according to the format:
	 * </p>
	 * <code>
	 * <i>
	 * <b>Request        =</b> Simple-Request | Full-Request
	 * <br><br>
	 * <b>Simple-Request =</b> "GET" SP Request-URI CRLF
	 * <br>
	 * <b>Full-Request   =</b> Request-Line              
	 *                   *( General-Header         
	 *                    | Request-Header         
	 *                    | Entity-Header )        
	 *                   CRLF
	 *                   [ Entity-Body ]          
	 * </i>
	 * </code>
	 * 
	 * @param msg
	 *            Http Message
	 * @throws InvalidCreationException
	 *             When the Http message is invalid
	 */
	public HttpMessage(byte[] msg) throws InvalidCreationException {
		message = msg;
		parseMessage();
	}

	public HttpMessage() {
	};

	/**
	 * <p>
	 * Parses Http Message according to the format:
	 * </p>
	 * <code>
	 * <i>
	 * Request        = Simple-Request | Full-Request
	 * <br><br>
	 * Simple-Request = "GET" SP Request-URI CRLF
	 * <br><br>
	 * Full-Request   = Request-Line              
	 *                   *( General-Header         
	 *                    | Request-Header         
	 *                    | Entity-Header )        
	 *                   CRLF
	 *                   [ Entity-Body ]          
	 * </i>
	 * </code>
	 * 
	 * @throws InvalidCreationException
	 *             When the Http message is invalid
	 */
	public void parseMessage() throws InvalidCreationException {
		if (message == null) {
			throw new InvalidCreationException("Http Message is Null.");
		}
		String myMessage = new String(message);

		// String[] msgLines = myMessage.split("\n");

		// Checking if it's a request or response
		if (myMessage.trim().indexOf(ProxyConstants.HTTP_VERSION_EXP) == 0) {
			parseResponse();
		} else {
			parseRequest();
		}
	}

	private void parseRequest() throws InvalidCreationException {
		try {

			String charSetName = getCharSet();
			String myMessage = null;
			try {
				myMessage = new String(message, charSetName);
			} catch (UnsupportedEncodingException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

			String[] msgLines = myMessage.split("\n");

			String[] methodNUrl = msgLines[0].split(" ");

			method = methodNUrl[0];

			url = methodNUrl[1];

			// Parsing url
			String[] hostNPort = null;
			if (url.indexOf("http://") == 0) {
				String[] urlParts = url.split("/+");
				hostNPort = urlParts[1].split(":");
			} else {
				hostNPort = url.split(":");

			}
			host = hostNPort[0];
			if (hostNPort.length > 1) {
				port = Integer.valueOf(hostNPort[1]);
			}

			// Parsing headers
			boolean endOfHeaders = false;
			int pos = 2;
			while (pos < msgLines.length && !endOfHeaders) {
				if (msgLines[pos].matches("\\s+")) {
					endOfHeaders = true;
				} else {
					String keyNValue[] = msgLines[pos].split(": *");
					if (keyNValue.length > 1) {
						headers.put(keyNValue[0], keyNValue[1]);
					}
				}
				pos++;
			}

			// Parsing Body
			StringBuilder body = new StringBuilder();
			while (pos < msgLines.length) {
				body.append(msgLines[pos]);
				pos++;
			}

			soapMessage = parseBodyAsSoap(body.toString());

			if (soapMessage != null) {
				wsCall = true;
			}

			if (logger.isLoggable(Level.FINEST)) {
				logger.log(Level.FINEST, "Request Body:\n" + body.toString());
			}

		} catch (IndexOutOfBoundsException iobe) {
			InvalidCreationException ex = new InvalidCreationException(
					"Invalid HTTP Message. Header Corrupted.");
			ex.initCause(iobe);
			throw ex;
		}
	}

	private String getCharSet() {
		String myMessage = new String(message);
		int csPosStart = myMessage.indexOf(ProxyConstants.CHARSET_PROP);
		int csPosEnd = myMessage.indexOf("\r\n", csPosStart);
		String csProperty = myMessage.substring(
				csPosStart + ProxyConstants.CHARSET_PROP.length(), csPosEnd)
				.toUpperCase();
		return csProperty.split("=\\s*")[1];
	}

	/**
	 * <p>
	 * Parses body message and check if its a valid Soap Message.
	 * </p>
	 * 
	 * @param msg
	 *            Body message
	 * @return message SoapMessage parsed
	 */
	private Document parseBodyAsSoap(String msg) {

		InputStream in = new ByteArrayInputStream(msg.getBytes());
		DocumentBuilder builder = null;
		Document doc = null;
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			factory.setNamespaceAware(true);
			builder = factory.newDocumentBuilder();

			doc = builder.parse(in);
			doc.normalizeDocument();

			boolean hasEnvelope = doc.getElementsByTagNameNS("*",
					ProxyConstants.SOAP_ENVELOPE).getLength() == 1;
			boolean hasBody = doc.getElementsByTagNameNS("*",
					ProxyConstants.SOAP_BODY).getLength() == 1;

			if (hasEnvelope && hasBody) {
				wsCall = true;
			} else {
				if (logger.isLoggable(Level.INFO)) {
					logger
							.log(
									Level.INFO,
									"Invalid or Absent SOAP Message in the packet Body. Handling as a simple HTTP Packet.");
				}
			}

		} catch (ParserConfigurationException pce) {
			if (logger.isLoggable(Level.INFO)) {
				logger
						.log(
								Level.INFO,
								"Invalid or Absent SOAP Message in the packet Body. Handling as a simple HTTP Packet.");
			}
		} catch (SAXException se) {
			if (logger.isLoggable(Level.INFO)) {
				logger
						.log(
								Level.INFO,
								"Invalid or Absent SOAP Message in the packet Body. Handling as a simple HTTP Packet.");
			}
		} catch (IOException ioe) {
			if (logger.isLoggable(Level.INFO)) {
				logger
						.log(
								Level.INFO,
								"Invalid or Absent SOAP Message in the packet Body. Handling as a simple HTTP Packet.");
			}
		}
		return doc;
	}

	private void parseResponse() {
		if (logger.isLoggable(Level.FINEST)) {
			logger.log(Level.FINEST, "Parsing Http Response");
		}
		// Parsing Response
	}

	public void appendBytes(byte[] chunk, int length) {
		byte[] newBuffer = null;
		if (message == null) {
			newBuffer = new byte[length];
		} else {
			newBuffer = new byte[message.length + length];
			for (int i = 0; i < message.length; i++)
				newBuffer[i] = message[i];
		}

		for (int i = 0; i < length; i++) {
			newBuffer[i] = chunk[i];
		}
		message = newBuffer;
	}

	public String getMethod() {
		return method;
	}

	public String getBody() {
		return body;
	}

	public String getUrl() {
		return url;
	}

	public byte[] getMessage() {
		return message;
	}

	public boolean isWsCall() {
		return wsCall;
	}

	public int getPort() {
		return port;
	}

	public String getHost() {
		return host;
	}

	public Document getSoapMessage() {
		return soapMessage;
	}
}
